<?php
declare(strict_types=1);

/**
 * Created by PhpStorm.
 * User: 小灰灰
 * Date: 2023-02-22
 * Time: 9:41:08
 * Info: 后台登录
 */

namespace app\admin\controller;

use app\common\controller\AdminController;
use think\facade\Db;
use think\facade\Cache;
use think\facade\Event;
use think\facade\View;
use think\captcha\facade\Captcha;
use app\common\model\Admin as AdminModel;
use app\common\model\LoginLog as LoginLogModel;
use app\event\Logout;
use app\common\service\admin\CacheClearService;

class Login extends Base
{

    /**
     * 初始化函数
     * @return void
     */
    public function initialize(): void
    {
        parent::initialize();
        //获取当前的操作名
        $action  = $this->request->action();
        $adminId = cmf_get_admin_id();
        //查看session是否有admin或者是否是退出操作
        if ( ! empty(isUserLoggedIn($adminId)) && ! in_array($action, ['logout'], true)) {
            $this->success(__('AlreadyLoggedIn,NoNeedToLoginAgain'), [], 0, url('admin/index/index'));
        }
    }

    /**
     * 后台登录
     * @return string|\think\response\View
     */
    public function index()
    {
        $loginCaptcha = get_config('login_captcha');
        View::assign('login_captcha', $loginCaptcha);

        return View::fetch();
    }

    /**
     * 校验登录
     * @return void
     * @throws \think\db\exception\DataNotFoundException    数据没找到异常错误
     * @throws \think\db\exception\DbException              数据库连接异常错误
     * @throws \think\db\exception\ModelNotFoundException   model模型没找到异常错误
     */
    public function checkLogin(): void
    {
        //获取参数
        $param = $this->request->param();

        //验证验证码
        $this->validateCaptcha($param);

        //验证用户名和密码
        $this->validateCredentials($param);

        //获取admin表的查询数据
        $adminInfo = AdminModel::where('username', $param['username'])->find();

        if (empty($adminInfo)) {
            $this->handleLoginFailure($param['username'], $param['password'], __('UsernameNotExist'));

            return;
        }

        //判断登录错误次数，禁止N秒内继续登录：
        if ($this->isAccountLocked($adminInfo)) {
            $lockCount = get_config('lock_count');
            $lockTime  = get_config('lock_time');
            $leftTime  = $lockTime - (time() - $adminInfo['logintime']);
            $errorMsg  = sprintf(lang('PasswordErrorLock'), $lockCount, $leftTime);
            $this->error($errorMsg);
        }

        //校验密码
        if ( ! passwordVerify($param['password'], $adminInfo['password'])) {
            $this->handlePasswordError($adminInfo, $param['password']);

            return;
        }

        //判断该用户是否被禁用
        if ($adminInfo['status'] != 'normal') {
            $this->handleAccountDisabled($adminInfo);

            return;
        }

        //登录成功处理
        $this->handleLoginSuccess($adminInfo, $param);
    }

    /**
     * 获取后台登录验证码
     * @return \think\Response
     */
    public function captcha(): \think\Response
    {
        return Captcha::create();
    }

    /**
     * 退出登录
     * @return void
     */
    public function logout(): void
    {
        $adminId = cmf_get_admin_id();

        //清除历史缓存
        try {
            CacheClearService::cacheClearThis($adminId);
        } catch (\Exception $e) {
            //var_dump($e->getMessage());
        }

        //清空admin的session数据
        cmf_admin_logout($adminId);

        //触发退出登录事件
        Event::trigger('admin_logout_after', $this->request);
        $this->success(lang('SignOut').lang('Success'));
    }

    /**
     * 验证验证码
     *
     * @param array $param
     *
     * @return void
     */
    private function validateCaptcha(array $param): void
    {
        $loginCaptcha = get_config('login_captcha');
        if ($loginCaptcha == 1) {
            //验证码必填
            if (empty($param['captcha'])) {
                $this->error(__('%s CantBeEmpty', [__('Captcha')]));
            }
            if ( ! captcha_check($param['captcha'])) {
                $this->error(__('%s Incorrect', [__('Captcha')]));
            }
        }
    }

    /**
     * 验证用户名和密码
     *
     * @param array $param
     *
     * @return void
     */
    private function validateCredentials(array $param): void
    {
        //验证用户名
        if (empty($param['username'])) {
            $this->error(__('%s CantBeEmpty', [__('Username')]));
        }
        //验证密码
        if (empty($param['password'])) {
            $this->error(__('%s CantBeEmpty', [__('Password')]));
        }
    }

    /**
     * 处理登录失败
     *
     * @param string $username
     * @param string $password
     * @param string $errorMsg
     *
     * @return void
     */
    private function handleLoginFailure(string $username, string $password, string $errorMsg): void
    {
        //获取用户失败，拼接日志data
        $msg = json_encode([
            'msg'      => $errorMsg,
            'username' => $username ?: "",
            'password' => $password
        ], JSON_UNESCAPED_UNICODE);

        //写入登录日志
        $this->addLoginLog(0, $username, 0, $msg);
        $this->error(__('%s Incorrect', [__('UsernameOrPassword')]));
    }

    /**
     * 检查账户是否被锁定
     *
     * @param AdminModel $adminInfo
     *
     * @return bool
     */
    private function isAccountLocked(AdminModel $adminInfo): bool
    {
        $lockCount = get_config('lock_count');
        $lockTime  = get_config('lock_time');

        if ( ! empty($lockCount) && $adminInfo['login_failure'] >= $lockCount && $lockTime > 0) {
            $leftTime = $lockTime - (time() - $adminInfo['logintime']);

            return $leftTime > 0;
        }

        return false;
    }

    /**
     * 处理密码错误
     *
     * @param AdminModel $adminInfo
     * @param string     $password
     *
     * @return void
     */
    private function handlePasswordError(AdminModel $adminInfo, string $password): void
    {
        //拼接日志data
        $msg = json_encode([
            'msg'      => __('%s Incorrect', [__('Password')]),
            'username' => $adminInfo['username'],
            'password' => $password
        ], JSON_UNESCAPED_UNICODE);

        //写入失败次数
        AdminModel::editLoginFailure($adminInfo['id'], 1);

        //写入登陆日志
        $this->addLoginLog($adminInfo['id'], $adminInfo['username'], 0, $msg);
        $this->error(__('%s Incorrect', [__('UsernameOrPassword')]));
    }

    /**
     * 处理账户被禁用
     *
     * @param AdminModel $adminInfo
     *
     * @return void
     */
    private function handleAccountDisabled(AdminModel $adminInfo): void
    {
        //拼接日志data
        $msg = json_encode([
            'msg'      => __('AccountDisabled'),
            'username' => $adminInfo['username'],
            'password' => "******"
        ], JSON_UNESCAPED_UNICODE);

        //写入登陆日志
        $this->addLoginLog($adminInfo['id'], $adminInfo['username'], 0, $msg);
        $this->error(__('AccountDisabled'));
    }

    /**
     * 处理登录成功
     *
     * @param AdminModel $adminInfo
     * @param array      $param
     *
     * @return void
     */
    private function handleLoginSuccess(AdminModel $adminInfo, array $param): void
    {
        //获取客户端IP地址
        $adminInfo->loginip = get_client_ip();
        //获取登录时间
        $adminInfo->logintime = time();
        $adminInfo->login_num = Db::raw('login_num+1');
        //写入数据库
        $adminInfo->save();
        //转换数据格式
        $adminData = $adminInfo->toArray();

        //保持登录状态
        $adminData['expire_time'] = ( ! empty($param['keep_login']) && $param['keep_login'] == 'on') ? true : time() + 3600 * 2;

        //清除密码防止泄露
        unset($adminData['password']);

        //拼接日志data
        $msg = json_encode([
            'msg'      => __('Login').__('Success'),
            'username' => $adminData['username'],
            'password' => "******"
        ], JSON_UNESCAPED_UNICODE);

        //更新登录错误次数为0
        AdminModel::editLoginFailure($adminData['id']);

        //写入登录日志
        $this->addLoginLog($adminData['id'], $adminData['username'], 1, $msg);

        //触发登录成功事件
        Event::trigger('admin_login_after', $this->request);

        //清除历史缓存
        CacheClearService::cacheClearThis($adminData['id']);

        //写入登录session
        cmf_set_current_admin($adminData['id'], $adminData);
        // 清除该用户之前的菜单缓存
        Cache::tag('initAdmin_'.$adminData['id'])->clear();

        $this->success(__('Login').__('Success'));
    }

    /**
     * 写入登录日志
     *
     * @param int    $adminId   日志操作者id
     * @param string $adminName 日志操作者用户名
     * @param int    $status    状态：1=成功；0=失败
     * @param string $desc      日志描述
     *
     * @return void
     */
    private function addLoginLog(int $adminId, string $adminName, int $status, string $desc = ''): void
    {
        //后台登录日志开关
        $loginLogAdmin = get_config('login_log_admin');
        if ( ! $loginLogAdmin) {
            return;
        }

        //获取用户的相关信息的，包括用户使用的浏览器，操作系统等信息
        $agent      = $this->request->server('HTTP_USER_AGENT', '');
        $clientInfo = get_client_browser($agent);
        $osInfo     = get_client_os($agent);

        //获取客户端IP地址
        $ipAddress = get_client_ip();

        $param = [
            'user_id'     => $adminId,
            'user_name'   => $adminName,
            'browser'     => $clientInfo['browser'] ?? "",
            'browser_ver' => $clientInfo['browser_ver'] ?? "",
            'os'          => $osInfo['os'] ?? "",
            'os_ver'      => $osInfo['os_ver'] ?? "",
            'ip_address'  => $ipAddress,
            'status'      => $status,
            'desc'        => $desc
        ];

        //写入后台登录日志
        LoginLogModel::createLoginLog($param);
    }
}
